package digitRecognitionProblem.reduceConnections;

import genetic_algorithm.Chromosome;
import genetic_algorithm.FitnessFunction;

import java.io.File;
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;

import mlp.MlpExamples;
import mlp.SparseMlp;
import utils.RandomGenerator;

public class ReduceConFitness implements FitnessFunction {

	public static String MLP_FILE = ""; // file holding a serialized fully-connected neural network
	private int trainSize; // number of train examples
	private MlpExamples trainExamples; // examples used to train networks generated out of chromosomes	
	private MlpExamples testExamples; // examples used to measure networks fitness
	private int epochs; // number of iterations of back propagations while training a new network
	
	/**
	 * Constructor- creates a new object to evaluate
	 * chromosome's fitness
	 * @param trainSize number of train examples
	 * @param testExamples examples used to measure networks fitness
	 * @param epochs for how many iterations trains offsprings
	 */
	public ReduceConFitness(int trainSize, MlpExamples testExamples, int epochs) {
		super();
		this.trainSize = trainSize;
		this.trainExamples = null;
		this.testExamples = testExamples;
		this.epochs = epochs;
	}
	
	/**
	 * Calculates chromosome's fitness according to its
	 * neural network mean squared error 
	 */
	@Override
	public double getFitness(Chromosome chromosome) {
		
		// generate neural network out of chromosome's data				
		SparseMlp mlp = decode(chromosome);

		// parse train examples to be used
		int offset = RandomGenerator.nextInt(38000 / trainSize);
		setTrainExamples(offset);

		// re-train network
		mlp.learn(trainExamples.getInput(), trainExamples.getOutput(), epochs);
		
		// test its fitness
		float mse = mlp.evaluateQuadraticError(testExamples.getInput(),
				testExamples.getOutput())
				/ ((float) testExamples.getInput().size());
		return 1.0 /(mse + 1.0);
	}

	private SparseMlp decode(Chromosome chromosome) {
		
		// get binary data
		ReduceConChromosome mlpArgs = (ReduceConChromosome) chromosome;
		BitSet bits = mlpArgs.getData();
		
		// extract switch value for each edge of hidden layer
		boolean[][] hiddenLayerSwitch = new boolean[SparseMlp.HIDDEN_LAYYER][SparseMlp.INPUTS];
		int numInputs = SparseMlp.INPUTS; // number of input edges is number of inputs for the network
		for (int currNeuron = 0 ; currNeuron < SparseMlp.HIDDEN_LAYYER ; ++currNeuron) {
			for (int currEdge = 0 ; currEdge < SparseMlp.INPUTS ; ++currEdge) {
			
				// offset is #neuron * #edge_for_each_neuron + #edge
				hiddenLayerSwitch[currNeuron][currEdge] = (bits.get(currNeuron
						* numInputs + currEdge)) ? true : false;
			}
		}
		
		// extract switch value for each edge of output layer
		boolean[][] outputLayerSwitch = new boolean[SparseMlp.OUTPUT_LAYYER][SparseMlp.HIDDEN_LAYYER];
		numInputs =  SparseMlp.HIDDEN_LAYYER; // number of input edges is number of neurons in hidden layer
		int prevLayerOffset = SparseMlp.HIDDEN_LAYYER * SparseMlp.INPUTS; // offset to skip previous layer's data = #neurons * #edges of hidden layer
		for (int currNeuron = 0 ; currNeuron < SparseMlp.OUTPUT_LAYYER ; ++currNeuron) {
			for (int currEdge = 0 ; currEdge < SparseMlp.HIDDEN_LAYYER ; ++currEdge) {
				
				// offset is |data_for_previous_layer| + #neuron * #edge_for_each_neuron + #edge
				outputLayerSwitch[currNeuron][currEdge] = (bits
						.get(prevLayerOffset + currNeuron * numInputs
								+ currEdge)) ? true : false;
			}
		}
		
		// create the switch data for all layers
		List<boolean[][]> switchData = new ArrayList<boolean[][]>(2);
		switchData.add(hiddenLayerSwitch);
		switchData.add(outputLayerSwitch);

		// extract the trained network
		SparseMlp mlp = null;
		try {
			FileInputStream readFile = new FileInputStream(new File(MLP_FILE));
			ObjectInputStream fileStream = new ObjectInputStream(readFile);
			mlp = (SparseMlp)fileStream.readObject();
			fileStream.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		// turn off indicated edges
		mlp.setSwitch(switchData);
		return mlp;
	}

	private void setTrainExamples(int offset) {
		trainExamples = new MlpExamples(ReduceConMain.TRAIN_FILE, offset, trainSize, ReduceConMain.DIGIT);
	}
}
